package org.internna.ossmoney.services.impl;

import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Currency;
import java.math.BigDecimal;
import org.internna.ossmoney.model.Bill;
import org.internna.ossmoney.model.Payee;
import org.internna.ossmoney.model.Account;
import org.internna.ossmoney.util.DateUtils;
import org.internna.ossmoney.model.AccountType;
import org.internna.ossmoney.model.Subcategory;
import org.internna.ossmoney.model.support.Interval;
import org.internna.ossmoney.model.AccountTransaction;
import org.internna.ossmoney.model.FinancialInstitution;
import org.internna.ossmoney.model.security.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.transaction.annotation.Transactional;

@Component
@Transactional
public final class AccountService implements org.internna.ossmoney.services.AccountService {

    @Override public void transferMoney(Long origin, Long target, Date operationDate, BigDecimal amount, BigDecimal chargeAmount, double rate, String memo) {
        UserDetails user = UserDetails.findCurrentUser();
        Account originAccount = Account.findAccount(origin);
        Account targetAccount = Account.findAccount(target);
        transferMoney(user, originAccount, targetAccount, operationDate, amount, chargeAmount, rate, memo);
    }

    protected void transferMoney(UserDetails user, Account origin, Account target, Date operationDate, BigDecimal amount, BigDecimal chargeAmount, double rate, String memo) {
        if (isValidTransfer(user, origin, target, operationDate, amount)) {
            Payee payee = Payee.findMySelf(user);
            Subcategory transferCategory = Subcategory.findBySubcategory("category.transfer.out", user);
            Subcategory transferInCategory = Subcategory.findBySubcategory("category.transfer.in", user);
            AccountTransaction send = AccountTransaction.createInstance(origin, payee, transferCategory, calculateAmount(amount), operationDate, memo);
            AccountTransaction receive = AccountTransaction.createInstance(target, payee, transferInCategory, calculateTargetAmount(amount, rate), operationDate, memo);
            receive.setOriginOfTheFunds(origin.getName());
            send.persist();
            receive.persist();
            if ((chargeAmount != null) && (!BigDecimal.ZERO.equals(chargeAmount))) {
            	Subcategory chargeCategory = Subcategory.findBySubcategory("category.bankcharges", user);
                AccountTransaction charge = AccountTransaction.createInstance(origin, Payee.findByFinancialInstitution(origin.getHeldAt()), chargeCategory, calculateAmount(chargeAmount), DateUtils.nextDate(operationDate), memo);
                charge.persist();
            }
            Date now = new Date();
            origin.setLastModified(now);
            target.setLastModified(now);
            origin.merge();
            target.merge();
        }
    }

    protected boolean isValidTransfer(UserDetails user, Account origin, Account target, Date operationDate, BigDecimal amount) {
        return isValidTransaction(user, origin, operationDate, amount) && isValidTransaction(user, target, operationDate, amount);
    }

    @Override public long addTransaction(AccountTransaction transaction) {
        long accountId = -1;
        UserDetails user = UserDetails.findCurrentUser();
        if (transaction.getAccount() != null) {
            accountId = transaction.getAccount().getId();
            Account account = Account.findAccount(accountId);
            if (isValidTransaction(user, account, transaction.getOperationDate(), transaction.getAmount())) {
                transaction.setAccount(account);
                if (transaction.getPayee() != null) {
                    transaction.setPayee(Payee.entityManager().getReference(Payee.class, transaction.getPayee().getId()));
                }
                if (transaction.getSubcategory() != null) {
                    transaction.setSubcategory(Subcategory.entityManager().getReference(Subcategory.class, transaction.getSubcategory().getId()));
                }
                transaction.persist();
                account.setLastModified(new Date());
                account.merge();
            }
        }
        return accountId;
    }

    protected boolean isValidTransaction(UserDetails user, Account account, Date operationDate, BigDecimal amount) {
        boolean valid = (account != null) && (user != null);
        if (valid) {
            valid = account.belongsTo(user);
            valid &= (account.getClosed() != null) && !account.getClosed();
            valid &= (operationDate != null) && operationDate.after(account.getOpened());
            valid &= (amount != null) && !BigDecimal.ZERO.equals(amount);
        }
        return valid;
    }

    @Override public void createAccount(Account account) {
        if (account != null) {
            Date now = new Date();
            account.setCreated(now);
            account.setLastModified(now);
            account.setClosed(Boolean.FALSE);
            UserDetails user = UserDetails.findCurrentUser();
            user.addAccount(account);
            if (account.getFavorite() == null) {
                account.setFavorite(Boolean.FALSE);
            }
            if (account.getAccountType() != null) {
                account.setAccountType(AccountType.entityManager().getReference(AccountType.class, account.getAccountType().getId()));
            }
            if (account.getHeldAt() != null) {
            	FinancialInstitution institution = FinancialInstitution.findFinancialInstitution(account.getHeldAt().getId()); 
                institution.addAccount(account);
            }
            if ((account.getInitialBalance() == null) || (account.getInitialBalance().doubleValue() < 0)) {
                account.setInitialBalance(BigDecimal.ZERO);
            }
            account.persist();
            user.merge();
        }
    }

    protected BigDecimal calculateAmount(BigDecimal amount) {
        BigDecimal calculated = amount.abs();
        return calculated.multiply(new BigDecimal(-1));
    }

    protected BigDecimal calculateTargetAmount(BigDecimal amount, double exchangeRate) {
        BigDecimal calculated = amount.abs();
        return calculated.multiply(new BigDecimal(Math.abs(exchangeRate)));
    }

	@Override public BigDecimal getExpenses(UserDetails user, Locale locale, Subcategory category, Interval interval) {
		BigDecimal expenses = BigDecimal.ZERO;
		Currency currency = Currency.getInstance(locale);
		for (Account account : user.getAccounts()) {
			Currency accountCurrency = Currency.getInstance(account.getLocale()); 
			if (currency.equals(accountCurrency)) {
				List<AccountTransaction> transactions = account.getTransactionsInPeriod(interval, null);
				if (!CollectionUtils.isEmpty(transactions)) {
					for (AccountTransaction transaction : transactions) {
						if (transaction.getSubcategory().equals(category)) {
							expenses = expenses.add(transaction.getAmount().abs());
						}
					}
				}
			}
		}
		return expenses;
	}

	@Override public void balance(UserDetails user, Account account, Account origin, Date date, String[] transactions) {
		BigDecimal amount = BigDecimal.ZERO;
		for (String id : transactions) {
			AccountTransaction transaction = AccountTransaction.findAccountTransaction(Long.parseLong(id));
			transaction.setReconciled(Boolean.TRUE);
			amount = amount.add(transaction.getAmount().abs());
			transaction.merge();
		}
		Subcategory transferCategory = Subcategory.findBySubcategory("category.transfer.out", user);
		AccountTransaction transfer = AccountTransaction.createInstance(origin, Payee.findByName(account.getName()), transferCategory, amount, date, null);
		transfer.persist();
		Subcategory transferInCategory = Subcategory.findBySubcategory("category.transfer.in", user);
		AccountTransaction incomeTransfer = AccountTransaction.createInstance(account, Payee.findMySelf(user), transferInCategory, amount, date, null);
		incomeTransfer.setOriginOfTheFunds(origin.getName());
		incomeTransfer.persist();
	}

	@Override public void payBill(UserDetails user, Bill bill, Long origin, Double amount, Date operationDate) {
		Account account = Account.findAccount(origin);
		BigDecimal total = new BigDecimal(amount).abs().negate();
		AccountTransaction transaction = AccountTransaction.createInstance(account, bill.getPayee(), bill.getCategory(), total, operationDate, "");
		bill.updatePayment(operationDate);
		bill.merge();
		transaction.persist();
	}

}
